iOS多线程 - Pthread

1. 多线程的原理

线程是可并发执行的,拥有最小系统资源,共享进程资源的基本调度单位。

共有堆,自有栈 , iOS 主线程栈和其他线程均默认为 512K。

堆可以理解为操作系统提供的一块内存,可以随意使用,在我们使用 new , alloc时,分配的内存就是系统用堆提供的。

栈是操作系统给当前的应用程序分配的,仅供当前应用程序使用的内存空间,也就是给我们的线程分配的内存空间,这个空间是有限制的,它主要是用来定义一些函数内部的成员变量,栈里的数据其他程序不能访问。

这也就是说,不要定义很大的局部变量,如果一个函数的成员变量,是一个数组,这个数组大于 512K,那么很可能程序根本不能运行。

在实际开发中,线程最好保持在 3-5 条,过多的线程会消耗大量 CPU 资源,单个线程被调度的执行频次会降低,因此线程并不是越多越好。

并发执行进度不可控,我们创建的线程何时执行是由操作系统调度,比如说我们有5个线程,每个线程打印一句话,那么在每次运行程序时,这5句话的打印顺序可能都不一样,这导致了两个潜在的风险。

对非原子操作容易造成状态不一致

  • 这是说在我们使用多线程时,加入同一个变量进行访问,有的读,有的写,如果没有很好的控制机制的话,读到的值可能并不是需要的值。那么为了处理这种情况,我们会使用加锁控制,但这又会引起另一个风险。

加锁控制有死锁的风险

  • 加锁控制是在读或写之前,通常是在写之前,首先给这个变量加一把锁,加锁以后其他进程无法读到,在写的操作完成之后,再把锁解开,其他的进程才可以进行读的操作。

    这个风险在于,当其他进程也有锁时,当前进程需要读一个变量,读到之后才能进行写的操作,但是这个变量的锁需要等当前进程的锁打开,两个进程都在等对方解锁,这就触发了死锁的问题。


2. iOS中主要的多线程技术

技术方案 简介 语言 线程声明周期
pthread 一套通用的多线程API,跨平台可移植,使用难度大。 C 程序员管理
NSThread 使用更加向对象,简单易用可直接操作线程对象。 OC 程序员管理
GCD 旨在替代NSThread等线程技术,充分理由设备多核。 C 自动管理
NSOperation 基于GCD,多了一些实用功能,使用更加面向对象。 OC

3. 多线程的使用

当我们在 UI 线程,也就是主线程中,执行一个耗时操作时,因为线程是串行的,所以会导致 UI 界面有无法刷新,无法响应等问题,例如我们使用一个 Button,响应函数执行一个for 循环,那么在循环完成之前,Button 是灰色的。

1
2
3
4
5
6
7
8
9
10
11
#pragma mark 所以要使用多线程技术
- (IBAction)BtnClick:(id)sender {
//耗时操作 在这个循环执行完以前 button 始终处于灰色不可用的状态
//如果我们在 UI线程 也就是主线程中执行耗时操作 会导致 UI界面 不能刷新 或者说无法响应
for (int i=0; i<5000; i++) {
//格式化字符串
NSString *str = [NSString stringWithFormat:@"i = %d",i];
NSLog(str,nil);
}
}

这个例子告诉我们,不要把耗时操作放在主线程/UI线程中

那么,如何解决这个问题呢?这就用到了我们的多线程技术。

  • 引入 #import <pthread.h> 头文件,因为是系统文件,所以要使用尖括号引入。
  • BtnClick方法里创建线程。
1
2
pthread_t threadId;
pthread_create(&threadId, NULL, ThreadFunc, NULL);
  • 创建线程的pthread_create()方法的四个参数意义如下:
类型 功能 填写
pthread_t * 线程的id,线程创建后,可通过id操作线程。 传入自己声明的属性
const pthread_attr_t * 返回线程的属性 ,一般不需要。 填NULL
void ()(void *) 函数指针, 这是线程需要执行的方法。 自己写一个方法
void * 上一个参数线程方法的传入值 可以是NULL
  • 创建要线程的执行的方法,也就是pthread_create()的第三个参数:
1
2
3
4
5
6
7
8
9
void *ThreadFunc (void *pParam) {
//把这个循环从原来的 BtnClick 剪切到这个方法里 使用子线程执行
for (int i=0; i<5000; i++) {
//格式化字符串
NSString *str = [NSString stringWithFormat:@"i = %d",i];
NSLog(str,nil);
}
return NULL;
}
  • 验证是否有两个线程在运行:
1
2
3
4
//获取当前的线程
NSThread *thread = [NSThread currentThread];
NSLog(@"主线程 %@",thread);
//将这段代码放在 BtnClick 方法里
1
2
3
4
NSThread *thread = [NSThread currentThread];
thread.name = @"我的线程";
NSLog(@"子线程 %@",thread);
//将这段代码放在 ThreadFunc 方法里
  • 打印结果:
1
2
主线程 <NSThread: 0x7bf6a9a0>{number = 1, name = main}
子线程 <NSThread: 0x7e073750>{number = 3, name = 我的线程}

可以看出,有两个线程,而且主线程的 number=1 , name=main

并且点击 Buttonfor 循环进行的同时,Button 的状态并不受影响。

到这里我们就实现了一个简单的 pthread 线程。